#ifndef SCOREMATRIX_H
#define SCOREMATRIX_H

#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <assert.h>
#include <exception>

#include "Score.h"
using namespace std;

template < class T > 
class Array3D 
{
public:
	void ***mat_ptr;
	int mats, rows, cols;

public:
	Array3D(){mat_ptr=0; mats=rows=cols=0;};
	Array3D(int mats, int rows, int cols){ mat_ptr=0; resize(mats, rows, cols); }
	~Array3D() {free( mat_ptr);}
	void resize(int mats, int rows, int cols){
		if (0==mats || 0==rows || 0==cols) {
			cerr << " ERROR: Array3D-resize()" << mats << " mats; " << rows << " rows; " << cols << " cols." << endl;
  			throw; // assert (0);
			//exit(0);
		}
		this->mats=mats; this->rows = rows; this->cols=cols;
		if (mat_ptr) free( mat_ptr);
		int header=mats*sizeof(T*);
		int header1=mats*rows*sizeof(T*);
		int data=mats*rows*cols*sizeof(T);
		mat_ptr = (void***)malloc(header+header1+data);
		memset(mat_ptr, 0, header+header1+data);
		void** header_buf=(void**)(mat_ptr+mats); // second-level pointers
		T* buf=(T*)(header_buf+mats*rows);
		for (int k=0; k<mats; k++) {
			mat_ptr[k]= header_buf+k*rows;
			for (int l=0; l<rows; l++)
				mat_ptr[k][l]=buf + (k*rows+l) * cols;
		}
	}
	T &operator() (int mat, int row, int col) throw (int,bad_exception) {
		if (mat>=mats || row>=rows || col>=cols || mats<0 || row<0 || col<0) {
			cerr << "ERROR: Array3D()-&op() " << mat << " vs. " << mats << ";" << row << " vs. " << rows << "; " << col << " vs. " << cols<< endl;
  			throw 'x'; // assert (0);
			//exit(0);
		}
		return ((T***)mat_ptr)[mat][row][col];
	}
	T operator() (int mat, int row, int col) const {
		if (mat>=mats || row>=rows || col>=cols || mats<0 || row<0 || col<0) {
			cerr << "ERROR: Array3D()-op()" << mat << " vs. " << mats << ";" << row << " vs. " << rows << "; " << col << " vs. " << cols<< endl;
  			throw; // assert (0);
			//exit(0);
		}
		return ((T***)mat_ptr)[mat][row][col];
	}
};

class ScoreMatrix {
public:
	int layers, id;
	double helper;
	int rows;
	int cols;
	Score default_value;
	void **rowptr;
	string name;

public:
	ScoreMatrix (){rowptr=0; rows=cols=0;default_value=0; name="";};
	ScoreMatrix (int rows, int cols, string n){ rowptr=0; default_value=0; name=n; resize(rows, cols); layers=0; id=0; helper=0;}
	/* Copy constructor */
	ScoreMatrix (const ScoreMatrix &m) : rows(m.rows), cols(m.cols), default_value(m.default_value), rowptr(m.rowptr){}
	~ScoreMatrix(){free( rowptr);}

	inline void resize(int rows, int cols){
		if (0==rows || 0==cols) {
			cerr << " ERROR: ScoreMatrix("<<name<<")-resize()" << rows << " rows; " << cols << " cols." << endl;
  			throw; // assert (0);
			//exit(0);
		}
		this->rows = rows; this->cols=cols;
		if (rowptr) free( rowptr);
		int header=rows*sizeof(Score*);
		int data=rows*cols*sizeof(Score);
		rowptr = (void**)malloc(header+data);
		memset(rowptr, 0, header+data);
		Score* buf=(Score*)(rowptr+rows);
		for (int k=0; k<rows; k++) {
			rowptr[k]= buf+k*cols;
		}
	}
/* Fill all matrix values */
	void Fill(const Score &value){ 
		default_value=value; 
		Score* buf=(Score*)(rowptr+rows);
		for (int i = 0; i < rows * cols; i++) 
			buf[i] = value;
	}
	Score* &operator() (int row){
		if (row>=rows || row<0) {
			cerr << "ERROR: ScoreMatrix("<<name<<")-* &op() " << row << " vs. " << rows << endl;
  			throw; // assert (0);
			//exit(0);
		}
		return ((Score**)rowptr)[row];
	}
	Score* operator() (int row) const {
		if (row>=rows || row<0) {
			cerr << "ERROR: ScoreMatrix("<<name<<")-* op()" << row << " vs. " << rows << endl;
  			throw; // assert (0);
			//exit(0);
		}
		return ((Score**)rowptr)[row];
	}
	Score &operator() (int row, int col) throw (int,bad_exception) {
		if (row>=rows || col>=cols || row<0 || col<0) {
			cerr << layers <<" " << id << " " << helper << "ERROR: ScoreMatrix("<<name<<")-&op() " << row << " vs. " << rows << "; " << col << " vs. " << cols<< endl;
  			throw 'x'; // assert (0);
			//exit(0);
		}
		return ((Score**)rowptr)[row][col];
	}
	Score operator() (int row, int col) const {
		if (row>=rows || col>=cols || row<0 || col<0) {
			cerr << "ERROR: ScoreMatrix("<<name<<")-op()" << row << " vs. " << rows << "; " << col << " vs. " << cols<< endl;
  			throw; // assert (0);
			//exit(0);
		}
		return ((Score**)rowptr)[row][col];
	}

	void set(int row, int col, Score score){
		if (row>=rows || col>=cols || row<0 || col<0) {
			cerr << "ERROR: ScoreMatrix("<<name<<")-set " << row << " vs. " << rows << "; " << col << " vs. " << cols<< endl;
  			assert (0);
			//exit(0);
		}
		((Score**)rowptr)[row][col] = score;
	}

	Score add(int row, int col, Score score){
		if (row>=rows || col>=cols || row<0 || col<0) {
			cerr << "ERROR: ScoreMatrix("<<name<<")-add " << row << " vs. " << rows << "; " << col << " vs. " << cols<< endl;
  			assert (0);
			//exit(0);
		}
		Score* pRow=((Score**)rowptr)[row];
		pRow[col] += score;
		return pRow[col];
	}

	void divide(int row, int col, Score score){
		if (row>=rows || col>=cols || row<0 || col<0) {
			cerr << "ERROR: ScoreMatrix("<<name<<")-divide " << row << " vs. " << rows << "; " << col << " vs. " << cols<< endl;
  			assert (0);
			//exit(0);
		}
		((Score**)rowptr)[row][col] /= score;
	}

	void clearRow(int row){ memset(((Score**)rowptr)[row], 0, sizeof(Score)*cols); }

	void row_copy(int row, Score* dt){
		memcpy(((Score**)rowptr)[row], dt, sizeof(Score)*cols);
	}

	void row_divide(int row, Score scale){
		Score* pRow=((Score**)rowptr)[row];
		for (int j=0; j<cols; j++)
			pRow[j]/=scale;
	}

	Score row_normalize(int row){
		Score scale=0;
		Score* pRow=((Score**)rowptr)[row];
		for (int j=0; j<cols; j++)
			scale+=pRow[j];
		for (int j=0; j<cols; j++){
			if (pRow[j]==0) continue;
			pRow[j]/=scale;
		}
		return scale;
	}
};

#endif
